from cStringIO import StringIO
from struct import Struct as _Struct


class ConstructError(Exception):
    pass
class FieldError(ConstructError):
    pass


class Construct(object):
    def __init__(self, name):
        assert name is None or type(name) is str
        self.name = name
    def parse(self, buf):
        return self._parse(StringIO(buf), {})
    def build(self, obj):
        stream = StringIO()
        self._build(obj, stream, {})
        return stream.getvalue()
    def _parse(self, stream, context):
        raise NotImplementedError
    def _build(self, obj, stream, context):
        raise NotImplementedError

class Subconstruct(Construct):
    def __init__(self, subcon):
        Construct.__init__(self, subcon.name)
        self.subcon = subcon
    def _parse(self, stream, context):
        return self.subcon._parse(stream, context)
    def _build(self, obj, stream, context):
        self.subcon._build(obj, stream, context)

class Adapter(Subconstruct):
    def _parse(self, stream, context):
        return self._decode(self.subcon._parse(stream, context))
    def _build(self, obj, stream, context):
        self.subcon._build(self._encode(obj), stream, context)
    def _encode(self, obj):
        raise NotImplementedError
    def _decode(self, obj):
        raise NotImplementedError

class Struct(Construct):
    def __init__(self, name, *subcons):
        Construct.__init__(self, name)
        self.subcons = subcons
    def _parse(self, stream, context):
        obj = {}
        for sc in self.subcons:
            subobj = sc._parse(stream, {"_" : context})
            if sc.name is not None:
                context[sc.name] = obj[sc.name] = subobj
        return obj
    def _build(self, obj, stream, context):
        for sc in self.subcons:
            if sc.name is None:
                subobj = None
            else:
                subobj = context[sc.name] = obj[sc.name]
            sc._build(subobj, stream, {"_" : context})

class Field(Construct):
    def __init__(self, name, length):
        Construct.__init__(self, name)
        self.length = length
    def _parse(self, stream, context):
        cnt = self.length(context) if callable(self.length) else self.length
        data = stream.read(cnt)
        if len(data) != cnt:
            raise FieldError("expected %d, found %d" % (cnt, len(data)))
        return data
    def _build(self, obj, stream, context):
        cnt = self.length(context) if callable(self.length) else self.length
        if len(obj) != cnt:
            raise FieldError("expected %d, found %d" % (cnt, len(obj)))
        stream.write(obj)

class NumericField(Field):
    def __init__(self, name, fmt):
        self.fmt = _Struct(fmt)
        Field.__init__(self, name, self.fmt.size)
    def _parse(self, stream, conext):
        return self.fmt.unpack(Field._parse(self, stream, conext))[0]
    def _build(self, obj, stream, conext):
        Field._build(self, self.fmt.pack(obj), stream, conext)

def UBInt8(name):
    return NumericField(name, ">B")
def UBInt16(name):
    return NumericField(name, ">H")
def UBInt32(name):
    return NumericField(name, ">L")
def UBInt64(name):
    return NumericField(name, ">Q")

class FormatContainer(Adapter):
    def __init__(self, subcon, *elems):
        Adapter.__init__(self, subcon)
        self.elems = elems
    def _encode(self, obj):
        obj2 = {}
        for e in self.elems:
            obj2[e.name] = e._encode(obj)
        return obj2
    def _decode(self, obj):
        e = self.elems[-1]
        return e._decode(obj[e.name])

class FormatElement(object):
    def __init__(self, name):
        self.name = name
    def _encode(self, obj):
        raise NotImplementedError
    def _decode(self, obj):
        raise NotImplementedError

class LenOf(FormatElement):
    def _encode(self, obj):
        return len(obj)
    def _decode(self, obj):
        return None

class Raw(FormatElement):
    def _encode(self, obj):
        return obj
    def _decode(self, obj):
        return obj

c = FormatContainer(
    Struct("pascal_string",
        UBInt8("length"),
        Field("data", lambda ctx: ctx["_"]["length"]),
    ),
    LenOf("length"),
    Raw("data"),
)

print repr(c.parse("\x05hello"))
print repr(c.build("kaki"))
















































    